package zemberek3.crosscheck;

import zemberek3.structure.TokenSequence;

/**
 * Base class for distance calculators.
 */
public abstract class BaseDistanceCalculator implements DistanceCalculator {

    protected void checkArguments(TokenSequence... sequences) {
        for (TokenSequence sequence : sequences) {
            if (sequence == null)
                throw new IllegalArgumentException("Token sequence is null!");
        }
    }

    public double distance(String source, String target) {
        return distance(new TokenSequence(source), new TokenSequence(target));
    }

    /**
     * token distance. used internally. System applies Levenshtein edit distance but if there is a substition for a word,
     * it gives a penalty calculated by StringDistance interface provided by the caller.
     *
     * @param source source String.
     * @param target target String
     * @param stringDistance  the interface for calculating distance between two tokens.
     * @return distance.
     */
    protected double sentenceDistance(
            String source,
            String target,
            StringDistance stringDistance) {
        return sentenceDistance(new TokenSequence(source), new TokenSequence(target), stringDistance);
    }

    /**
     * token distance. used internally. System applies Levenshtein edit distance but if there is a substition for a token,
     * it gives a penalty calculated by StringDistance interface provided by the caller.
     *
     * @param sourceSequence source sequence.
     * @param targetSequence target sequence
     * @param stringDistance  the interface for calculating distance between two tokens.
     * @return distance.
     */
    protected double sentenceDistance(
            TokenSequence sourceSequence,
            TokenSequence targetSequence,
            StringDistance stringDistance) {
        int n;
        double p[]; //'previous' cost array, horizontally
        double d[]; // cost array, horizontally
        double _d[]; //placeholder to assist in swapping p and d

        n = sourceSequence.size();
        p = new double[n + 1];
        d = new double[n + 1];

        final int m = targetSequence.size();
        if (n == 0 || m == 0) {
            if (n == m) {
                return 1;
            } else {
                return 0;
            }
        }

        // indexes into strings s and t
        int i; // iterates through s
        int j; // iterates through t

        String t_j; // jth string

        double cost; // cost

        for (i = 0; i <= n; i++) {
            p[i] = i;
        }

        for (j = 1; j <= m; j++) {
            t_j = targetSequence.get(j - 1);
            d[0] = j;

            for (i = 1; i <= n; i++) {
                cost = stringDistance.calculate(sourceSequence.get(i - 1), t_j);
                // minimum of cell to the left+1, to the top+1, diagonally left and up +cost
                d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
            }

            // copy current distance counts to 'previous row' distance counts
            _d = p;
            p = d;
            d = _d;
        }

        // our last action in the above loop was to switch d and p, so p now
        // actually has the most recent cost counts
        return p[n];
    }

    /**
     * calculates distance between two strings.
     */
    protected interface StringDistance {
        double calculate(String token1, String token2);
    }

}
